<!DOCTYPE html>
<script type="text/javascript">
    var protocol = location.protocol;
    var host = location.host;
    var isLocal = false;
    if (host.indexOf('127.0.0.1') > -1 || host.indexOf('localhost') > -1) isLocal = true;
    if (protocol != 'https:' && protocol.indexOf('file') == -1 && !isLocal) {
        location.href = 'https:' + location.href.substring(protocol.length);
    }

    console.log(/\w+.html$/.exec(window.location.href)[0]);

    if (navigator.userAgent.indexOf('Mobile') > -1 && !window.location.href.indexOf('m_') > -1) { // 移动端打开pc页面
        console.log('true')
        window.location.href = 'm_' + /\w+.html$/.exec(window.location.href)[0];
    } else if (!navigator.userAgent.indexOf('Mobile') > -1 && window.location.href.indexOf('m_') > -1) { // pc端打开移动端页面
        console.log('false')
        window.location.href = /\w+.html$/.exec(window.location.href)[0].replace('m_', '');
    }
</script>

<html>

<head>
    <meta charset="UTF-8">
    <title>云屋MeetingDemo</title>
    <meta name="renderer" content="webkit" />
    <meta http-equiv="pragma" content="no-cache">
    <meta http-equiv="cache-control" content="no-cache">
    <meta http-equiv="expires" content="0">
    <meta name="format-detection" content="telephone=no" />
    <meta name="keywords" content="云屋webRTC" />
    <meta name="description" content="云屋webRTC" />
    <link rel="shortcut icon" href="./image/favicon.ico">
    <link rel="stylesheet" href="../static/lib/layui/css/layui.css">
    <link rel="stylesheet" href="./css/meeting.css">
    <script type="text/javascript" src="../static/lib/layui/layui.js"></script>
    <script type="text/javascript" src="../static/lib/jquery.2.0.0.min.js"></script>

</head>

<body>
    <!-- 浏览器不支持提示 开始 -->
    <div class="alert-wrapper" style="display: none">
        <div class="mask"></div>
        <div class="alert-box alert-box-1">
            <div class="alert-header">
                <div class="title">提示</div>
                <div class="close" id="wrapperCloseBtn"></div>
            </div>
            <div class="alert-body">
                <div class="check-compatibility">
                    <i class="icon-tip"></i>
                    <div class="tip-content">
                        <div class="c1">当前浏览器不支持WebRTC，无法正常使用H5音视频功能。</div>
                        <div class="c3">请下载最新Chrome浏览器后重新打开此页面。</div>
                    </div>
                </div>
            </div>
            <div class="alert-footer">
                <button class="btn btn-longer f-mgr-10" id="downloadChromeBtn">立即下载</button>
                <button class="btn btn-cancle" id="laterDownloadBtn">稍后下载</button>
            </div>
        </div>
    </div>
    <script>
        // IE浏览器提示不受支持
        if (navigator.userAgent.indexOf("MSIE") > -1 || navigator.userAgent.indexOf("Trident") > -1) {
            $(".alert-wrapper").show();
            $("#downloadChromeBtn").click(function () {
                window.location.href = "https://www.baidu.com/s?wd=chrome";
            })
            $("#laterDownloadBtn").click(function () {
                $(".alert-wrapper").hide();
            })
            $("#wrapperCloseBtn").click(function () {
                $(".alert-wrapper").hide();
            })
        }
    </script>
    <!-- 浏览器不支持提示 结束 -->

    <!-- 登录界面 开始 -->
    <!-- <div id="page_login" oncontextmenu="return false;" onselectstart="return false" style="display:block"> -->
    <div id="page_login" onselectstart="return false">
        <!-- <video src="./20200218114723HDu3hhxqIT.mp4" id="videotest" autoplay loop="loop"></video> -->
        <div class="login_cont">
            <div class="login_top_logo">
                <img class="login_logo" src="./image/pc/login_icon.png" alt="">
            </div>
            <div class="login_form">
                <div class="form_label">请输入房间号</div>
                <div class="form_dec">
                    <span>如果该房间存在，您将自动加入正在进行的房间；</span>
                    <span>如果该房间不存在，请点击下面的【创建房间】按钮，</span>
                    <span>创建并自动加入到一个新的房间；</span>
                </div>
                <div class="form_input">
                    <label for="g_meet_id">房间号：</label>
                    <input type="text" id="g_meet_id" placeholder="请输入房间号" value="" />
                </div>
                <div class="form_input nickname">
                    <label for="userInputName">昵&ensp;&ensp;称：</label>
                    <input type="text" id="userInputName" placeholder="请输入昵称" value="" />
                </div>
                <div class="enmeet_btn" onclick="MeetingDemo.RoomMgr.onClickEnterBtn()">进入房间</div>
                <div class="login_order">
                    <span class="line"></span>
                    <span class="text">或者</span>
                    <span class="line"></span>
                </div>
                <div class="crmeet_btn" onclick="MeetingDemo.RoomMgr.onClickCreateBtn()">创建房间</div>
                <div class="loginSet">
                    <div id="login_sel" onselectstart="return false" onclick="MeetingDemo.Login.clickLoginSel()">登录设置
                    </div>
                    <div class="logset_hide">
                        <div class="token_radio">
                            <form action="">
                                <span>鉴权方式：</span>
                                <input id="loginPassRadio" type="radio" name="login_set" value=1 checked>
                                <label for="loginPassRadio">密码</label>&nbsp;
                                <input id="loginTokeRadio" type="radio" name="login_set" value=2>
                                <label for="loginTokeRadio">Token&nbsp;</label>
                            </form>
                        </div>
                        <div class="login_set_mstype">
                            <span>转发协议：</span>
                            <input id="msTypeUDPTCP" type="radio" name="msTypeSet" value=0>
                            <label for="msTypeUDPTCP">UDP/TCP</label>&ensp;
                            <input id="msTypeUDP" type="radio" name="msTypeSet" value=1 checked>
                            <label for="msTypeUDP">UDP</label>&ensp;
                            <input id="msTypeTCP" type="radio" name="msTypeSet" value=2>
                            <label for="msTypeTCP">TCP</label>
                        </div>
                        <div class="input_box">
                            <form action="">
                                <input type="password" style="display: none;">
                                <div class="form_input">
                                    <label for="server_add">服务器地址：</label>
                                    <input type="text" id="server_add" value="" />
                                </div>
                                <div class="form_input form-account">
                                    <label for="cr_account">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;AppID：</label>
                                    <input type="text" id="cr_account" placeholder="默认账号" value="默认APPID" />
                                </div>
                                <div class="form_input form-password">
                                    <label for="comp_psdw">AppSecret：</label>
                                    <input type="password" id="comp_psdw" placeholder="默认Secret" value="默认Secret" />
                                </div>
                                <div class="form_input form-token" style="display: none;">
                                    <label for="loginToken">&emsp;&emsp;Token：</label>
                                    <input type="text" id="loginToken" value="" />
                                </div>
                                <div class="form_input form-userAuth">
                                    <label for="loginToken">第三方鉴权：</label>
                                    <input type="password" id="userAuthCode" value="" onfocus="this.type='text'"
                                        placeholder="未启用第三方鉴权则无需填写" />
                                </div>
                            </form>
                        </div>
                        <div class="resetBtn bottom-btn form_input" onclick="MeetingDemo.Login.resetLoginParam()">重置
                        </div>
                        <div class="confirmBtn bottom-btn form_input" onclick="MeetingDemo.Login.saveLoginParam()">确定
                        </div>
                    </div>
                </div>
                <div class="form_sdkver">SDKVer：<span id="sdkver"></span></div>
            </div>
        </div>
    </div>
    <!-- 登录界面 结束 -->

    <!-- 会议界面 开始 -->
    <div id="page_meeting">
        <div class="page_meet_head por">
            <div class="w">
                <div class="page_meet_logo"><img src="./image/pc/meeting_header_icon-user.png" alt="">云屋视频会议Demo</div>
                <div class="page_meet_id">
                    <span id="meetNickName">昵称：xxxx</span>&ensp;&ensp;
                    <span id="meetId">房间号：88888888</span>
                </div>
                <div id="page_meet_record" class="page_meet_record icon">
                    <button id="recordBtn" onclick="MeetingDemo.Record.onClickRecordBtn()"><span>录制</span></button>
                </div>
                <div id="liveStream">
                    <label><input type="checkbox" id="liveStreamBtn"
                            onchange="MeetingDemo.Record.toggleLiveStream(this)">直播推流</label>
                    <input type="text" id="liveStreamVal" placeholder="推流地址">
                </div>
            </div>
        </div>
        <div class="por main" onselectstart="return false">
            <div class="page_meet_cont">
                <div class="page_meet_view">
                    <div class="page_meet_video">
                        <div class="page_screen" style="display:block">
                            <div class="screenbox screenBox1"></div>
                            <div class="screenbox screenBox2"></div>
                            <div class="screenbox screenBox3"></div>
                            <div class="screenbox screenBox4"></div>
                            <div class="screenbox screenBox5"></div>
                            <div class="screenbox screenBox6"></div>
                            <div class="screenbox screenBox7"></div>
                            <div class="screenbox screenBox8"></div>
                            <div class="screenbox screenBox9"></div>
                        </div>
                        <div class="page_chat_box">
                            <ul>
                                <!-- <span>乖乖兔：这是干啥呢</span> -->
                            </ul>
                        </div>
                    </div>
                    <div class="page_share_box"></div>
                    <div class="page_meet_chat">
                        <input type="text" name="chat_msg" placeholder="请输入聊天内容" />
                        <span id="sendMsg" onclick="MeetingDemo.Chat.onClickSendBtn()">发送</span>
                    </div>
                    <div class="page_meet_tools por">
                        <ul class="page_meet_tool">
                            <li id="openChatBtn" onclick="MeetingDemo.Chat.onClickChatBtn(this)">
                                <span>聊天</span>
                            </li>
                            <li id="openMicBtn" class="mic_close" onclick="MeetingDemo.Audio.onClickMicOnOffBtn(this)">
                                <span>开关麦克风</span>
                            </li>
                            <li id="openSpeakerBtn" class="speaker_open"
                                onclick="MeetingDemo.Audio.onClickSpeakerOnOffBtn(this)">
                                <span>开关扬声器</span>
                            </li>
                            <li id="openCamBtn" class="cam_close" onclick="MeetingDemo.Video.onClickCamOnOffBtn(this)">
                                <span>开关摄像头</span>
                            </li>
                            <li id="openScreenBtn" onclick="MeetingDemo.Screen.onClickScreenShareBtn(this)">
                                <span>屏幕共享</span>
                                <div class="screenControl btnlist">
                                    <ul>
                                        <!-- <li id="startMarkBtn" onclick="MeetingDemo.Screen.onClickStartMarkBtn(event)">开启标注</li> -->
                                        <li id="stopScreenShareBtn"
                                            onclick="MeetingDemo.Screen.onClickStopScreenShareBtn(event)">结束共享</li>
                                    </ul>
                                </div>
                            </li>
                            <li id="openMediaBtn" onclick="MeetingDemo.Media.onClickMediaShareBtn(this)">
                                <span>影音共享</span>
                                <div class="mediaControl btnlist">
                                    <input type="file" id="mediaFileInput"
                                        onchange="MeetingDemo.Media.onLoadMediaFiles(this)">
                                    <ul>
                                        <li id="stopMediaBtn" onclick="MeetingDemo.Media.onClickStopMediaBtn(event)">
                                            停止播放</li>
                                    </ul>
                                </div>
                            </li>
                            <li id="openInviteBtn" onclick="MeetingDemo.Invite.openInviteBtn()">
                                <span>邀请参会</span>
                            </li>
                            <li id="openSetBtn" onclick="MeetingDemo.onClickSetBtn()">
                                <span>设置</span>
                            </li>
                        </ul>
                        <div class="page_meet_close" onclick="MeetingDemo.RoomMgr.onClickCloseBtn()"></div>
                    </div>
                    <div class="page_open_set">
                        <div class="page_set_title">设置
                            <img src="./image/pc/meeting_close_setting.png" onclick="$('.page_open_set').hide()" />
                        </div>
                        <div class="page_set_form">
                            <label>摄像头：</label>
                            <select id="select_cam" onchange="MeetingDemo.Video.setDefaultVideo(this.value)"></select>
                        </div>
                        <div class="page_set_form">
                            <label>扬声器：</label>
                            <select id="select_speaker"
                                onchange="MeetingDemo.Audio.setAudioCongfig({speakerID:this.value})"></select>
                        </div>
                        <div class="page_set_form">
                            <label>麦克风：</label>
                            <select id="select_mic"
                                onchange="MeetingDemo.Audio.setAudioCongfig({micID:this.value})"></select>
                        </div>
                        <div class="page_set_form">
                            <label>分辨率：</label>
                            <select id="select_size" onchange="MeetingDemo.Video.setVideoConfig({size:this.value})">
                                <option value="1" selected="true">流畅</option>
                                <option value="2">标清</option>
                                <option value="3">高清</option>
                                <option value="4">超清</option>
                            </select>
                        </div>
                        <div class="page_set_form">
                            <label>帧&emsp;率：</label>
                            <select id="select_frame" onchange="MeetingDemo.Video.setVideoConfig({fps:this.value})">
                                <option value="5">5帧/秒</option>
                                <option value="10">10帧/秒</option>
                                <option value="15">15帧/秒</option>
                                <option value="20" selected="true">20帧/秒</option>
                                <option value="25">25帧/秒</option>
                                <option value="30">30帧/秒</option>
                            </select>
                        </div>
                        <!-- <div class="page_set_confirm">
                                <span id="setDeviceCfg">保存</span>
                            </div> -->
                        <div class="page_set_checkbox">
                            <input type="checkbox" id="mutiCam" onchange="MeetingDemo.Video.setEnableMutiVideo()"><label
                                for="mutiCam"> 启用多摄像头</label>
                            <!-- &ensp;&ensp; -->
                            <!-- <input type="checkbox" id="voiceTrack" onchange="MeetingDemo.RoomMgr.setMainVideoMode()"><label for="voiceTrack"> 语音激励</label> -->
                        </div>
                    </div>
                </div>
            </div>

        </div>
    </div>
    <!-- 会议界面 结束 -->

    <!-- 设置rem根字体大小 -->
    <script type="text/javascript">
        document.documentElement.style.fontSize = window.innerHeight ? Math.ceil(window.innerHeight / 950 * 100) +
            'px' :
            Math.ceil(document.documentElement.clientHeight / 950 * 100) + 'px';
        window.onresize = function () {
            document.documentElement.style.fontSize = window.innerHeight ? Math.ceil(window.innerHeight / 950 *
                100) +
                'px' :
                Math.ceil(document.documentElement.clientHeight / 950 * 100) + 'px';
        }
    </script>

    <script>
        var global = {
            window: window
        }; // 兼容electron
    </script>

    <script type="text/javascript" src="../static/lib/md5.js"></script>
    <script type="text/javascript" src="../static/lib/rem.min.js"></script>

    <script type="text/javascript" src="../SDK/RTCSDKV2.min.js?v=2.2.0.2"></script>
    <!-- <script src="../js/sdkDev.js"></script> -->

</body>

<script>
    layui.use(['form', 'layer', 'laytpl'], function () {
        // 初始化layui模块，注册相关事件监听
        window.layuiForm = layui.form;
        window.layer = layui.layer;
        window.laytpl = layui.laytpl;

        // 全局模块
        const _MeetingDemo = function () {
            // 加载层索引
            this.loadingIndex = null; // layui加载层索引，关闭加载层时需要
            // 会议中关闭页面弹出提示确认
            window.addEventListener("beforeunload", (e) => {
                if (MeetingDemo.RoomMgr.isMeeting) {
                    // 部分浏览器支持 event.preventDefault() + return 部分浏览器支持 event.returnValue
                    const event = e || window.event;
                    const message = "正在会议中，仍然关闭？";
                    event.preventDefault();
                    if (e) event.returnValue = message;
                    // 注册一个耗时计算的方法，让页面关的慢一点，确保离开房间的消息能发出去，这里先注册方法
                    window.__fib = function (n) {
                        return n < 2 ? n : __fib(n - 1) + __fib(n - 2);
                    };
                    return message;
                }
            });

            // 会议中确认关闭页面之后离开会议
            window.addEventListener("unload", (e) => {
                if (MeetingDemo.RoomMgr.isMeeting) {
                    CRVideo_ExitMeeting(); // SDK接口：退出房间
                    CRVideo_Logout(); // SDK接口：退出登录
                    // 做一个耗时计算，让页面关的慢一点，确保离开房间的消息能发出去，这里触发方法，一定是在接口调用完之后再触发
                    window.__fib(40);
                }
            });

            // 鼠标移入工具栏效果
            $(".page_meet_tool li").mouseover(function () {
                // $(this).find("span").show();
                $(this).find("span").show().siblings().find("span").hide();
            }).mouseout(function () {
                // $(this).find("span").hide();
                // $(this).siblings().find("span").hide();
                $(this).find("span").hide().siblings().find("span").show();
            })

            // 页面加载先载入本地存储的房间号
            $("#g_meet_id").val(localStorage.getItem("CRMeeting_meetingID"));

            // 监听键盘事件
            window.onkeypress = (event) => {
                var e = event || window.event || arguments.callee.caller.arguments[0];
                // 按下enter 键
                if (e && e.keyCode == 13) {
                    this.onpressEnter();
                }
            }
            // 弹出layui警告
            this.alertLayer = (contents, yesFun) => {
                window.layer && this.removeLoading(); // 移除loading层
                window.layer.open({
                    type: 0,
                    time: 0,
                    // area: 'auto',
                    title: ['提示', 'font-size:14px;'],
                    content: contents,
                    btn: ['确定'],
                    yes: function (index, layero) {
                        yesFun != undefined ? yesFun() : "";
                        layer.close(index);
                    }
                });
            }
            // 弹出layui提示
            this.tipLayer = (msg, timer) => {
                window.layer && this.removeLoading(); // 移除loading层
                if (timer == undefined) timer = 3000;
                window.layer.open({
                    title: 0,
                    btn: 0,
                    content: msg,
                    closeBtn: 0,
                    shade: 0,
                    time: timer,
                });
            }
            // 弹出模态框
            this.modalLayer = (title, content, btnsObj, time) => {
                let btns = btnsObj.btns,
                    btn1Callback = btnsObj.btn1Callback,
                    btn2Callback = btnsObj.btn2Callback;
                window.layer.open({
                    closeBtn: 0,
                    time: time !== undefined ? time : 0,
                    title: [title, 'font-size:14px'],
                    content: content,
                    btn: btns,
                    btn1: function (index) {
                        btn1Callback();
                        layer.close(index);
                    },
                    btn2: function (index) {
                        btn2Callback();
                    }
                })
            }
            // 弹出输入框
            this.promptLayer = (formType, value, title, yesCallback) => {
                window.layer.prompt({
                    formType: formType || 2,
                    value: value || '', // 默认值
                    title: title || '请输入',
                    // area: ['300px', '300px'] //自定义文本域宽高
                }, function (value, index, elem) {
                    yesCallback(value);
                    layer.close(index);
                });
            }
            // 弹出layui加载ing
            this.loadingLayer = (style, options) => {
                window.layer && this.removeLoading(); // 移除loading层
                if (!!style) { // style为 0 undefined null '' 等
                    style = 0;
                }
                if (options == undefined) {
                    options = {
                        time: 15000 // 最多15秒自动关闭
                    }
                }
                this.loadingIndex = window.layer.load(style, options);
            }
            // 移除layui加载
            this.removeLoading = () => {
                window.layer.close(this.loadingIndex);
            }
            // 移除弹出层
            this.closeLayer = (type) => {
                window.layer.closeAll(type == undefined ? '' : type);
            }
            // 判断是否是数组类型
            this.isArrayFn = (value) => {
                if (typeof Array.isArray === "function") {
                    return Array.isArray(value);
                } else {
                    return Object.prototype.toString.call(value) === "[object Array]";
                }
            }
            // 获取当前时间
            this.getDate = () => {
                const date = new Date();
                let year = date.getFullYear(),
                    month = date.getMonth() + 1,
                    day = date.getDate(),
                    hour = date.getHours(),
                    minute = date.getMinutes(),
                    second = date.getSeconds();

                month = month < 10 ? ('0' + month) : month;
                day = day < 10 ? ('0' + day) : day;
                hour = hour < 10 ? ('0' + hour) : hour;
                minute = minute < 10 ? ('0' + minute) : minute;
                second = second < 10 ? ('0' + second) : second;

                return {
                    year,
                    month,
                    day,
                    hour,
                    minute,
                    second,
                }
            }
            // 处理按下enter键
            this.onpressEnter = () => {
                if (MeetingDemo.RoomMgr.isMeeting && $(".page_meet_chat").css("display") !== "none") {
                    if ($("input[name=chat_msg]").val() !== "") {
                        $("#sendMsg").click(); // 发送消息
                    } else {
                        $("input[name=chat_msg]")[0].focus(); // 获得焦点
                    }
                }
            }
            // 刷新分屏样式
            this.refreshScreenSplit = () => {
                let number = 0;
                MeetingDemo.MemberMgr.members.forEach(member => {
                    member.allVideos.forEach(videoItem => {
                        if (videoItem.videoUI) {
                            number++;
                        }
                    })
                })
                if (number == 0 || number == 1) { // 1分屏
                    $(".page_screen").removeClass().addClass("page_screen screenOne");
                    $(".page_screen").children().eq(0).show().nextAll().hide();
                } else if (number == 2) { // 2分屏
                    $(".page_screen").removeClass().addClass("page_screen screenTwo");
                    $(".page_screen").children().eq(1).show().prevAll().show();
                    $(".page_screen").children().eq(1).show().nextAll().hide();
                } else if (number > 2 && number < 5) { // 4分屏
                    $(".page_screen").removeClass().addClass("page_screen screenFour");
                    $(".page_screen").children().eq(3).show().prevAll().show();
                    $(".page_screen").children().eq(3).show().nextAll().hide();
                } else if (number > 4 && number < 10) { // 9分屏
                    $(".page_screen").removeClass().addClass("page_screen screenNine");
                    $(".page_screen").children().show();
                }
            }
            // 判断是否在全屏状态
            this.isFullScreen = () => {
                return document.isFullScreen || document.mozIsFullScreen || document.webkitIsFullScreen
            }
            // 获取当前全屏的元素
            this.getFullscreenElement = () => {
                var fullscreenEle = document.fullscreenElement ||
                    document.mozFullScreenElement ||
                    document.webkitFullscreenElement;
                //注意：要在用户授权全屏后才能获取全屏的元素，否则 fullscreenEle为null
                fullscreenEle !== null ? console.log("全屏元素：", fullscreenEle) : "";
                return fullscreenEle;
            }
            // 退出全屏
            this.exitFullscreen = () => {
                if (!this.isFullScreen()) return;
                console.log(`退出全屏`);
                if (document.exitFullscreen) {
                    document.exitFullscreen();
                } else if (document.mozCancelFullScreen) {
                    document.mozCancelFullScreen();
                } else if (document.msExitFullscreen) {
                    document.msExiFullscreen();
                } else if (document.webkitCancelFullScreen) {
                    document.webkitCancelFullScreen();
                }
            }
            // 点击设置按钮
            this.onClickSetBtn = () => {
                if ($(".page_open_set").css("display") == "none") {
                    MeetingDemo.Video.updateVideoDevices();
                    MeetingDemo.Audio.updateAudioDevices();
                    $(".page_open_set").show();
                } else {
                    $(".page_open_set").hide();
                }
            }
        }
        const MeetingDemo = window.MeetingDemo = new _MeetingDemo();

        // 登录模块
        class _loginM {
            constructor() {
                this.initData();
                this.registerCallback();
                this.initDomEvent();
                $('#sdkver').text(CRVideo_GetSDKVersion());
                setTimeout(() => {
                    this.initSDK(); // 页面加载完自动初始化并登录
                }, 0);
            }
            initData() {
                this.isSetServer = false;
                this.storage = JSON.parse(sessionStorage.getItem('CRMEETING_PC_LOGIN')) || {}; // 存储在session的登录参数
                this.server = this.storage.server || window.location.host; // 登录的服务器地址
                if (this.server.includes('127.0.0.1') || this.server.includes('localhost') || !this.server) this
                    .server = 'sdk.cloudroom.com';
                this.authType = this.storage.authType || $('input[name=login_set]:checked')
                    .val(); // 鉴权方式 "1" appID鉴权 "2" token鉴权
                this.appID = this.storage.appID || $('#cr_account').val(); // 登录的账号（appID）
                this.appSecret = this.storage.appSecret || $('#comp_psdw').val(); // 登录的密码
                this.token = this.storage.token || $('#loginToken').val(); // token鉴权方式登录的token
                this.protocol = this.storage.protocol || $('input[name=msTypeSet]:checked').val(); // 流媒体转发协议
                this.userAuthCode = this.storage.userAuthCode || $('#userAuthCode').val(); // 启用第三方鉴时的鉴权参数
                this.nickname = this.storage.nickname || `H5_${Math.floor(Math.random() * 8999) + 1000}`; // 登录昵称
                this.userID = this.storage.userID || `${this.nickname}`; // 登录SDK系统的用户唯一ID
                this.isLogin = false; // 是否已经登录
                this.reLoginTimer = null; // 掉线重登计时器
                this.reLoginTimes = null; // 掉线重登重试次数

                $('#userInputName').val(this.nickname);
            }
            registerCallback() {
                // SDK接口：回调 登录成功
                CRVideo_LoginSuccess.callback = (UID, cookie) => {
                    this.isLogin = true;
                    this.reLoginTimes = 0; // 重置重登次数
                    this.setLoginStorage();
                    $('.login_form').css('border', '1px solid green');
                    $('#meetNickName').text(`昵称：${this.nickname}`);
                    console.log(`登录成功：${UID}`);
                    MeetingDemo.tipLayer(`登录成功：${UID}`);
                }
                // SDK接口：回调 登录失败
                CRVideo_LoginFail.callback = (sdkErr, cookie) => {
                    this.isLogin = false;
                    $('.login_form').css('border', 'none');
                    console.log(`登录服务器失败，错误码：${sdkErr}, cookie:${cookie}`);
                    if (!!cookie && cookie.includes('reLogin') && cookie.split('_')[1] <= 10) {
                        setTimeout(() => {
                            this.loginSystem(`reLogin_${++this.reLoginTimes}`);
                        }, 2000);
                        return;
                    } else if (!!cookie && cookie.includes('reLogin') && cookie.split('_')[1] > 10) {
                        $(".page_meet_close").click();
                        MeetingDemo.alertLayer(`网络错误，多次尝试重登失败，您已掉线！请在网络恢复后刷新页面重新登录...`);
                        return;
                    } else {
                        sdkErr == 7 ? sdkErr = "用户名或密码错误！" : sdkErr;
                        sdkErr == 14 ? sdkErr = "缺少第三方鉴权信息！" : sdkErr;
                        sdkErr == 15 ? sdkErr = "该项目没有启用第三方鉴权！" : sdkErr;
                        sdkErr == 17 ? sdkErr = "第三方鉴权失败！" : sdkErr;
                        sdkErr == 21 ? sdkErr = "Token鉴权失败！" : sdkErr;
                        sdkErr == 18 ? sdkErr = "Token已过期！" : sdkErr;
                        sdkErr == 20 ? sdkErr = "鉴权appID不存在！" : sdkErr;
                        sdkErr == 22 ? sdkErr = "此appID非Token鉴权！" : sdkErr;
                        sdkErr == 202 ? sdkErr = "登录服务器异常！" : sdkErr;
                        sdkErr == 204 ? sdkErr = "socket连接失败！" : sdkErr;
                        MeetingDemo.alertLayer('登录服务器失败，请稍后再试: ' + sdkErr);
                    }
                }
                // SDK接口：通知 Token即将失效
                CRVideo_NotifyTokenWillExpire.callback = () => {
                    layer.prompt({
                        formType: 2,
                        value: '',
                        title: 'Token即将失效，请立即更新!',
                        area: ['200px', '180px'] //自定义文本域宽高
                    }, function (value, index, elem) {
                        // SDK接口：更新Token
                        CRVideo_UpdateToken(value);
                        layer.close(index);
                    });
                }
                // SDK即可：通知 从服务器掉线了
                CRVideo_LineOff.callback = (sdkErr) => {
                    this.isLogin = false;
                    $('.login_form').css('border', 'none');
                    console.log(`从系统掉线了！错误码：${sdkErr}`);
                    if (sdkErr == 10) {
                        MeetingDemo.alertLayer(`您的ID已在其它设备登录，您已自动退出房间...`, () => {
                            MeetingDemo.RoomMgr.onClickCloseBtn(1);
                        });
                    } else if (sdkErr == 18) {
                        MeetingDemo.alertLayer(`Token失效，已从系统中断开！`);
                        MeetingDemo.RoomMgr.onClickCloseBtn(1);
                        $("#page_meeting").hide();
                        $("#page_login").show();
                    } else {
                        clearTimeout(this.reLoginTimer);
                        // this.reLoginTimer = setTimeout(() => {
                        //     console.log(`掉线，重新登录...`);
                        //     this.loginSystem(`reLogin_${++this.reLoginTimes}`);  // 这里不再自动重登呼叫系统了，只自动重试进入房间，进入房间之后再去判断一下
                        // }, 5000);
                    }
                }
            }
            // 注册事件
            initDomEvent() {
                // 切换token登录或密码登录
                $('input[name=login_set]').click((e) => {
                    const authType = e.target.value;
                    if (authType === '1') {
                        console.log(`切换成密码鉴权`);
                        $('.form-account').show();
                        $('.form-password').show();
                        $('.form-token').hide();
                    } else if (authType === '2') {
                        console.log(`切换成Token鉴权`);
                        $('.form-account').hide();
                        $('.form-password').hide();
                        $('.form-token').show();
                    }
                })

                // 输入昵称
                $('#userInputName').change((e) => {
                    this.nickname = e.target.value;
                    this.userID = this.nickname;
                    if (this.isLogin) {
                        this.logoutSystem();
                        this.loginSystem();
                    } else {
                        this.loginSystem();
                    }
                })
            }
            // 存储登录数据
            setLoginStorage() {
                const storage = {
                    server: this.server,
                    authType: this.authType,
                    appID: this.appID,
                    appSecret: this.appSecret,
                    token: this.token,
                    protocol: this.protocol,
                    userAuthCode: this.userAuthCode,
                    nickname: this.nickname,
                    userID: this.userID,
                }
                sessionStorage.setItem('CRMEETING_PC_LOGIN', JSON.stringify(storage));
            }
            // 点击登录设置
            clickLoginSel() {
                if ($(".logset_hide").css("display") == "none") {
                    console.log(this);
                    $('#server_add').val(this.server);
                    $('#cr_account').val(this.appID);
                    $('#comp_psdw').val(this.appSecret);
                    $('#userAuthCode').val(this.userAuthCode);
                    if (this.authType === '1') {
                        $('#loginPassRadio').click(); // 密码登录
                    } else {
                        $('#loginTokeRadio').click(); // Token登录
                    }
                    if (this.protocol === '0') {
                        $('#msTypeUDPTCP').click(); // UDP/TCP
                    } else if (this.protocol === '1') {
                        $('#msTypeUDP').click(); // UDP
                    } else {
                        $('#msTypeTCP').click(); // TCP
                    }
                    $('#loginToken').val(this.token);

                    $(".login_cont").css("top", "-230px")
                    $("#login_sel").css("background-image", 'url("./image/pc/login_arrows_up.png")');
                    $(".logset_hide").show();
                } else {
                    $(".login_cont").css("top", "0")
                    $("#login_sel").css("background-image", 'url("./image/pc/login_arrows_down.png")');
                    $(".logset_hide").hide();
                }
            }
            // 点击设置-重置按钮
            resetLoginParam() {
                const host = window.location.host; // 登录的服务器地址
                if (host.includes('127.0.0.1') || host.includes('localhost') || !host) {
                    this.server = 'sdk.cloudroom.com';
                } else {
                    this.server = location.host;
                }
                $('#server_add').val(this.server);
                $('#loginPassRadio').click(); // 密码登录
                $('#msTypeUDP').click(); // UDP
                $('#cr_account').val('默认APPID');
                $('#comp_psdw').val('默认Secret');
                $('#userAuthCode').val('')
                $('#loginToken').val('');

            }
            // 点击设置-确定按钮
            saveLoginParam() {
                if (!$('#server_add').val() || !$('#cr_account').val() || !$('#comp_psdw')) {
                    MeetingDemo.tipLayer('必要参数不能为空！');
                    return;
                }
                this.server = $('#server_add').val(); // 服务器地址
                this.authType = $('input[name=login_set]:checked').val(); // 鉴权方式 "1" appID鉴权 "2" token鉴权
                this.appID = $('#cr_account').val(); // 登录的账号（appID）
                this.appSecret = $('#comp_psdw').val(); // 登录的密码
                this.token = $('#loginToken').val(); // token鉴权方式登录的token
                this.protocol = $('input[name=msTypeSet]:checked').val(); // 流媒体转发协议
                this.userAuthCode = $('#userAuthCode').val(); // 启用第三方鉴时的鉴权参数
                let changed = false;
                const storage = JSON.parse(sessionStorage.getItem('CRMEETING_PC_LOGIN'));
                for (var key in storage) {
                    if (this[key] == undefined || this[key] != storage[key]) changed = true;
                }
                if (changed || this.isLogin == false) {
                    this.setLoginStorage();
                    this.logoutSystem();
                    this.loginSystem();
                }
                $('#login_sel').click();
            }
            // 初始化SDK
            initSDK() {
                // SDK接口：初始化SDK (新增接口 CRVideo_SetSDKParams 来设置SDK参数，因此初始化时可以不用再传参)
                CRVideo_Init().then(res => {
                    this.isInit = true;
                    this.loginSystem();
                }, err => {
                    MeetingDemo.tipLayer(`登录初始化失败：${err}`);
                })
            }
            // 登录系统
            loginSystem(cookie) {
                MeetingDemo.loadingLayer();
                // SDK接口：设置SDK参数
                CRVideo_SetSDKParams({
                    // isUploadLog: false,
                    // isSDKConsole: false,
                    videoSendBW: 1000,  // 视频接收端最大带宽（125KB/s）
                    screenSendBW: 1200, // 屏幕共享接收端最大带宽（150KB/s）
                    screenFps: 5, // 屏幕共享帧率
                    MSProtocol: this.protocol, // 媒体流传输协议 TCP UDP 详见文档
                    isCallSer: true,
                });
                // SDK接口：设置服务器地址 
                CRVideo_SetServerAddr(this.server);
                this.isSetServer = true;
                const nickname = this.nickname,
                    UID = this.userID,
                    code = this.userAuthCode;
                if (this.authType === '1') { // appID鉴权
                    let appID = this.appID,
                        appSecret = this.appSecret;
                    if (this.appID === '默认APPID' || this.appID === '') appID = 'demo@cloudroom.com';
                    if (this.appSecret === '默认Secret' || this.appSecret === '') appSecret = '123456';
                    // SDK接口：使用账号密码登录系统
                    CRVideo_Login(appID, md5(appSecret), nickname, UID, code, cookie);
                } else if (this.authType === '2') { // token鉴权
                    const token = this.token;
                    // SDK接口：使用Token登录系统
                    CRVideo_LoginByToken(token, nickname, UID, code, cookie);
                }
            }
            // 登出系统
            logoutSystem() {
                CRVideo_Logout();
                this.isLogin = false;
                console.log(this.isLogin);
            }
        }
        MeetingDemo.Login = new _loginM();

        // 房间管理模块
        class _roomMgr {
            constructor() {
                this.initData();
                this.registerCallback();
            }
            initData() {
                this.meetID = null; // 房间号
                this.isMeeting = false; // 当前是否正在会话中
                this.reEnterTimes = 0; // 进房间重试的次数
                this.reEnterTimer = null; // 掉线重登定时器

            }
            registerCallback() {
                // SDK接口:回调 创建房间成功
                CRVideo_CreateMeetingSuccess.callback = (meetObj, cookie) => {
                    console.log('创建房间成功', meetObj);
                    // 拿到房间号之后进入会议
                    this.meetID = meetObj.ID; // 存储会议信息
                    this.enterMeetingFun(); // 进入房间
                }
                // SDK接口：回调 创建房间失败
                CRVideo_CreateMeetingFail.callback = (sdkErr, cookie) => {
                    console.log('创建房间失败！', sdkErr);
                    MeetingDemo.alertLayer(`创建房间失败,请重试！错误码：${sdkErr}`)
                }
                // SDK接口：回调 进入房间的结果
                CRVideo_EnterMeetingRslt.callback = (sdkErr, cookie) => {
                    this.enterMeetingRslt(sdkErr, cookie);
                }
                // SDK接口：通知 房间被结束
                CRVideo_MeetingStopped.callback = (id) => {
                    MeetingDemo.alertLayer("<p>当前房间已被关闭！</p><p>请重新创建房间或进入其它正在进行中的房间。</p>", () => {
                        // window.location.reload(); // 刷新当前页面
                    })
                }
                // SDK接口：通知 主视频模式变化
                CRVideo_NotifyMainVideoModeChanged.callback = (vMode) => {
                    if (vMode == 1) { // 语音激励模式
                        // MeetingDemo.tipLayer('已开启语音激励模式');
                    } else if (vMode == 2) { // 跟随演示者模式
                        MeetingDemo.tipLayer('已关闭语音激励模式');
                    }
                }
                // SDK接口：通知 会议内主视频变化
                CRVideo_NotifyMainVideoChanged.callback = (UID) => {
                    MeetingDemo.MemberMgr.members.forEach(member => {
                        if (member.userID == UID) {
                            console.crlog(`[APP] 当前 ${member.nickname} 为主视频`)
                            MeetingDemo.tipLayer(`当前 ${member.nickname} 为主视频`);
                        }
                    })
                }
                // SDK接口：通知 从房间中掉线了
                CRVideo_MeetingDropped.callback = () => {
                    this.isMeeting = false;
                    this.reEnterTimer = setTimeout(() => {
                        MeetingDemo.tipLayer(`从房间中掉线了，正在第${this.reEnterTimes + 1}次尝试重新进入...`);
                        this.enterMeetingFun(`reEnter_${++this.reEnterTimes}`); // 进入房间
                    }, 2000);
                    return;
                }
            }
            // 点击创建房间按钮
            onClickCreateBtn() {
                if (!MeetingDemo.Login.isSetServer) return;
                MeetingDemo.loadingLayer();
                // SDK接口：创建会议
                CRVideo_CreateMeeting({
                    meetSubject: 'H5MeetingDemo',
                    needPswd: 0,
                    isRegular: 1
                });
            }
            // 点击进入房间按钮
            onClickEnterBtn() {
                if (!MeetingDemo.Login.isSetServer) return;
                MeetingDemo.loadingLayer();
                const MeetID = $("#g_meet_id").val();
                if (!MeetID) {
                    MeetingDemo.alertLayer("房间号不能为空！");
                    return;
                } else if (MeetID.length !== 8) {
                    MeetingDemo.alertLayer("请输入正确的8位房间号！");
                    return;
                } else {
                    this.meetID = MeetID;
                    this.enterMeetingFun(); // 进入房间
                }
            }
            // 进入房间的方法
            enterMeetingFun(cookie) {
                // SDK接口：进入房间
                CRVideo_EnterMeeting2(this.meetID, MeetingDemo.Login.userID, MeetingDemo.Login.nickname, cookie);
            }
            // 进入房间的结果
            enterMeetingRslt(sdkErr, cookie) {
                // 进入房间成功
                if (sdkErr == 0) {
                    console.log('进入房间成功', cookie);
                    if(!MeetingDemo.Login.isLogin) MeetingDemo.Login.loginSystem(); // 如果呼叫系统掉线了，就重登一下呼叫系统
                    this.isMeeting = true; // 是否正在会话中
                    this.reEnterTimes = 0; // 重试次数归零
                    if ($('#mutiCam')[0].checked == true) CRVideo_SetEnableMutiVideo(MeetingDemo.Login.userID, true); // SDK接口：设置是否启用多摄像头
                    MeetingDemo.Record.updateRecordStatue(); // 更新云端录制状态
                    localStorage.setItem("CRMeeting_meetingID", this.meetID) // 存储房间号
                    $("#g_meet_id").val(this.meetID); // 登录页面房间号输入
                    $("#meetId").text(`房间号：${this.meetID}`); // 会话界面展示房间号
                    // 更新所有媒体设备的信息，摄像头、麦克风、扬声器
                    MeetingDemo.Video.updateVideoDevices();
                    MeetingDemo.Audio.updateAudioDevices();
                    // 设置初始视频参数
                    MeetingDemo.Video.setVideoConfig({ size: +$('#select_size').val(), fps: +$('#select_frame').val() })
                    // 打开自己的摄像头和麦克风
                    MeetingDemo.Video.openVideo(MeetingDemo.Login.userID); // 打开自己的摄像头
                    MeetingDemo.Audio.openMic(MeetingDemo.Login.userID); // 打开自己的麦克风

                    MeetingDemo.Video.refreshUserVideoWall(); // 展示视频墙

                    MeetingDemo.removeLoading(); // 隐藏加载层
                    $("#page_login").hide(); // 隐藏登录页面
                    $("#page_meeting").css('display', 'flex');// 显示会话页面

                    // 进入房间失败
                } else {
                    console.log(`进入房间失败，错误码：${sdkErr}，cookie:${cookie}`);
                    // 通过cookie来判断是否是重登，如果是重登，网络恢复需要时间，可以多尝试几次
                    if (!!cookie && cookie.includes('reEnter') && cookie.split('_')[1] <= 10) {
                        this.reEnterTimer = setTimeout(() => {
                            MeetingDemo.tipLayer(`进入房间失败，正在第${this.reEnterTimes + 1}次尝试重新进入...`);
                            this.enterMeetingFun(`reEnter_${++this.reEnterTimes}`); // 进入房间
                        }, 1000);
                        return;
                    } else if (!!cookie && cookie.includes('reEnter') && cookie.split('_')[1] > 10) {
                        MeetingDemo.modalLayer(`失败`,`您已从房间中掉线，多次尝试重新进入房间失败，是否再次重试？`, {
                            btns: ['重试','取消'],
                            btn1Callback(){
                                MeetingDemo.RoomMgr.enterMeetingFun(`reEnter_${++MeetingDemo.RoomMgr.reEnterTimes}`); // 进入房间
                            },
                            btn2Callback(){
                                MeetingDemo.RoomMgr.onClickCloseBtn();
                            }
                        });
                    } else {
                        let errDesc = '';
                        switch (sdkErr) {
                            case 9:
                                errDesc = "登录状态异常";
                                break;
                            case 205:
                                errDesc = "没有可用服务器";
                                break;
                            case 800:
                                errDesc = "房间不存在或已结束";
                                break;
                            case 802:
                                errDesc = "服务器授权到期或超出并发数！";
                                break;
                            default:
                                errDesc = `请稍后再试！错误码：${sdkErr}`
                                break;
                        }
                        MeetingDemo.alertLayer(`进入房间失败！${errDesc}`);
                    }
                }
            }
            // 设置主视频模式（语音激励）
            setMainVideoMode() {
                if (!!$('#voiceTrack').prop('checked')) {
                    CRVideo_SetMainVideoMode(1, MeetingDemo.Login.userID); // 设置主视频模式  1 语音激励模式  2 跟随演示者  3 跟随特定参会者
                } else {
                    CRVideo_SetMainVideoMode(2, MeetingDemo.Login.userID);
                }
            }
            // 退出房间
            exitMeeting(iskick) {
                if (this.isMeeting) {
                    if (MeetingDemo.Record.isMyRecord) MeetingDemo.Record.stopRecord();
                    if (MeetingDemo.Screen.isMySharing) MeetingDemo.Screen.stopSharing();
                    !iskick && CRVideo_ExitMeeting(); // SDK接口：退出房间
                }

                $(".page_screen").removeClass().addClass("page_screen");
                $('.page_chat_box ul').html('');
                for (var i = 0; i < 9; i++) {
                    $(".page_screen").children().eq(i).html(`待进入`);
                }

                for (let key in MeetingDemo) {
                    if (MeetingDemo[key].initData !== undefined && key !== 'Login') {
                        MeetingDemo[key].initData();
                    }
                }

                $("#page_meeting").hide();
                $("#page_login").show();
            }
            // 点击退出按钮
            onClickCloseBtn(iskick) {
                this.exitMeeting(iskick);
            }

        }
        MeetingDemo.RoomMgr = new _roomMgr();

        // 成员管理模块
        class _memberM {
            constructor() {
                this.initData();
                this.registerCallback();
            }
            initData() {
                this.members = []; // 房间成员列表
            }
            registerCallback() {
                // SDK接口：通知 有新成员进入房间
                CRVideo_UserEnterMeeting.callback = (UID) => {
                    this.userEnter(UID);
                }
                // SDK接口：通知 有成员离开房间
                CRVideo_UserLeftMeeting.callback = (UID, reson) => {
                    this.userLeft(UID, reson);
                }
            }
            // 成员进入房间
            userEnter(UID) {
                this.refreshAllmembersInfo(); // 刷新成员列表
                MeetingDemo.Video.refreshUserVideoWall(); // 刷新视频墙
                if (UID != MeetingDemo.Login.userID) {
                    MeetingDemo.tipLayer(CRVideo_GetMemberNickName(UID) + ' 进入房间 ...', 2000);
                    if (MeetingDemo.Record.isMyRecord) MeetingDemo.Record.updateRecordContents();
                }
            }
            // 成员离开房间
            userLeft(UID, reson) {
                if (UID != MeetingDemo.Login.userID) {
                    try {
                        const leftUser = this.members.find(item => { return item.userID == UID });
                        MeetingDemo.tipLayer(`${leftUser.nickname} 离开房间...`, 2000);
                    } catch (e) {
                        throw e;
                    }
                } else {
                    if(reson == 'kick') {
                        MeetingDemo.alertLayer(`您已被踢出房间！`, ()=> {
                            MeetingDemo.RoomMgr.onClickCloseBtn(1);
                        })
                    }
                }
                this.refreshAllmembersInfo(); // 刷新成员列表
                MeetingDemo.Video.refreshUserVideoWall(); // 刷新视频墙
                if (MeetingDemo.Record.isMyRecord) MeetingDemo.Record.updateRecordContents();
            }
            // 刷新成员列表
            refreshAllmembersInfo() {
                const members = CRVideo_GetAllMembers(); // SDK接口：获取会议内所有成员信息
                members.forEach(memberItem => {
                    const user = this.members.find(item => memberItem.userID == item.userID)
                    if (user) { // 已经存在列表中的成员，更新状态
                        user.audioStatus = memberItem.audioStatus;
                        user.videoStatus = memberItem.videoStatus;
                    } else { // 列表中不存在的成员，添加
                        this.members.push(memberItem);
                    }
                })
                this.members.forEach((memberItem, index) => {
                    const user = members.find(item => item.userID == memberItem.userID);
                    if (!user) this.members.splice(index, 1); // 删除已经不在房间的成员
                })
                console.log('刷新成员列表：', this.members);
            }
            // 创建每个成员的状态控制图标
            createUserIcon(UID, videoID) {
                const htmlStr =
                    `<div class="deviceStatus" data-userid="${UID}" data-videoid="${videoID}">
                    <div class="deviceIcon deviceCam" onclick="MeetingDemo.Video.onClickUserIconCam(this)"></div>
                    <div class="deviceIcon deviceMic" onclick="MeetingDemo.Audio.onClickUserIconMic(this)"></div>
                    <div class="deviceIcon devicePhoto" onclick="MeetingDemo.Video.onClickUserIconSave(this)"></div>
                </div>`;
                const htmlEle = $(htmlStr)[0];
                return htmlEle;
            }
            // 更新某个成员的状态图标
            refreshUserIconStatus(UID) {
                const $deviceCam = $('[data-userid=' + UID + '] .deviceCam');
                const $deviceMic = $('[data-userid=' + UID + '] .deviceMic');
                const isSelf = UID == MeetingDemo.Login.userID ? true : false;
                this.members.forEach(item => {
                    if (item.userID == UID) {
                        if (item.videoStatus == 3) { // 开启
                            $deviceCam.addClass('deviceCamActive');
                            !!isSelf && $('#openCamBtn').addClass('cam_open').removeClass('cam_close');
                        } else { // 关闭
                            $deviceCam.removeClass('deviceCamActive');
                            !!isSelf && $('#openCamBtn').addClass('cam_close').removeClass('cam_open');
                        }
                        if (item.audioStatus == 3) { // 开启
                            $deviceMic.addClass('deviceMicActive');
                            !!isSelf && $('#openMicBtn').addClass('mic_open').removeClass('mic_close');
                        } else { // 关闭
                            $deviceMic.removeClass('deviceMicActive');
                            !!isSelf && $('#openMicBtn').addClass('mic_close').removeClass('mic_open');
                        }
                    }
                })
            }
        }
        MeetingDemo.MemberMgr = new _memberM();

        // 视频管理模块
        class _videoM {
            constructor() {
                this.initData();
                this.registerCallback();
            }
            initData() {
                // this.curCam = null; // 当前默认摄像头
                this.nameStyle = { // 视频容器昵称样式
                    left: "10px",
                    top: "10px",
                    backgroundColor: "rgba(0, 0, 0, .7)",
                    padding: "0 10px",
                    borderRadius: "15px",
                    display: "block"
                }
            }
            registerCallback() {
                // SDK接口：通知 成员的摄像头设备变化
                CRVideo_VideoDevChanged.callback = (UID) => {
                    this.memberVideoDevChange(UID);
                }
                // SDK接口：通知 成员的摄像头状态变化
                CRVideo_VideoStatusChanged.callback = (UID, oldStatus, newStatus) => {
                    this.memberVideoStatusChange(UID, oldStatus, newStatus);
                }
                // SDK接口：通知 成员的默认摄像头变化
                CRVideo_DefVideoChanged.callback = (UID, defaultCam) => {
                    // this.memberVideoDevChange(UID);
                }
            }
            // 更新摄像头信息并展示
            updateVideoDevices() {
                // SDK接口：获取成员的默认摄像头
                const defaultVideoID = CRVideo_GetDefaultVideo(MeetingDemo.Login.userID);
                // SDK接口：获取成员的所有摄像头
                const videoList = CRVideo_GetAllVideoInfo(MeetingDemo.Login.userID);
                let videoListOptionsStr = "";
                for (let i = 0; i < videoList.length; i++) {
                    if (defaultVideoID == videoList[i].videoID) {
                        videoListOptionsStr += "<option value=\"" + videoList[i].videoID + "\" selected=\"selected\" >" +
                            videoList[i].videoName + "</option>"
                    } else {
                        videoListOptionsStr += "<option value=\"" + videoList[i].videoID + "\" >" + videoList[i].videoName + "</option>"
                    }
                }
                $("#select_cam").html(videoListOptionsStr);
                // this.curCam = $('#select_cam').val();
            }
            // 打开用户的摄像头
            openVideo(UID) {
                // SDK接口：打开某个用户的摄像头
                CRVideo_OpenVideo(UID);
            }
            // 成员摄像头设备变化
            memberVideoDevChange(UID) {
                console.log(`${UID} 摄像头设备变化`);
                MeetingDemo.MemberMgr.members.forEach(item => {
                    if (item.userID == UID) {
                        const allVideoInfo = CRVideo_GetAllVideoInfo(UID);
                        const openedCams = CRVideo_GetOpenedVideoIDs(UID);
                        if (openedCams.length !== item.allVideos.length) {  // 数量有变化，得刷新视频墙
                            this.refreshUserVideoWall();
                        } else { // 数量没有变化，只需要重新setVideo即可
                            for (let i = 0; i < item.allVideos.length; i++) {
                                const openVideo = allVideoInfo.filter(videoItem => { return videoItem.videoID == openedCams[i] })[0];
                                item.allVideos[i].videoUI.setVideo(UID, openedCams[i]);
                                // item.allVideos[i].videoUI.setVideo(UID);
                                item.allVideos[i].videoID = openVideo.videoID;
                                item.allVideos[i].videoName = openVideo.videoName;
                            }
                        }
                    }
                })
                if (MeetingDemo.Record.isMyRecord) MeetingDemo.Record.updateRecordContents();
            }
            // 刷新视频墙
            refreshUserVideoWall() {
                console.log(`刷新视频墙`);

                // 如果每个用户重复创建了新的UI组件，则应该将不需要的旧组件销毁，避免内存泄漏
                // 在新组件没有创建完成前，不能销毁旧组件，否则会触发SDK的摄像头退订操作（内存中没有这个摄像头对应的UI组件了，SDK会退订这个摄像头）
                const destroyArr = [];
                MeetingDemo.MemberMgr.members.forEach(member => {
                    member.allVideos && member.allVideos.forEach(item => {
                        item.videoUI && destroyArr.push(item.videoUI);
                    })
                })
                setTimeout(() => {
                    destroyArr.length > 0 && console.log(`5s后销毁旧组件`);
                    destroyArr.length > 0 && destroyArr.forEach(item => {
                        // console.log(`${item._id} destroy()`);
                        item.destroy && item.destroy();
                    })
                }, 5000);

                // 配置每个成员的视频UI组件
                MeetingDemo.MemberMgr.refreshAllmembersInfo(); // 刷新成员列表
                // $(".page_screen").removeClass().addClass("page_screen");
                for (var i = 0; i < 9; i++) {
                    $(".page_screen").children().eq(i).html("待进入");
                }
                MeetingDemo.MemberMgr.members.length > 0 && MeetingDemo.MemberMgr.members.forEach((member, index) => {
                    const videoUIs = this.updateMemberVideoUI(member); // 成员视频UI组件处理
                    for (let j = 0; j < videoUIs.length; j++) {
                        for (var i = 0; i < 9; i++) {
                            if ($(".page_screen").children().eq(i).html().indexOf("待进入") > -1) {
                                $(".page_screen").children().eq(i).html(videoUIs[j].videoElement);
                                break;
                            }
                        }
                    }
                    MeetingDemo.MemberMgr.refreshUserIconStatus(member.userID); // 刷新图标样式
                });
                MeetingDemo.refreshScreenSplit(); // 刷新分屏样式


            }
            // 成员视频UI组件处理
            updateMemberVideoUI(member) {
                const allVideoInfo = CRVideo_GetAllVideoInfo(member.userID); // SDK接口：获取成员的所有摄像头信息
                const openedIDs = CRVideo_GetOpenedVideoIDs(member.userID) || []; // SDK接口：获取成员的当前打开的摄像头
                const openedVideos = allVideoInfo.filter(item => openedIDs.includes(item.videoID)); // 过滤出打开的摄像头
                const videoUIs = []; //  可能有多个摄像头同时开启的情况
                member.allVideos = openedVideos;
                member.allVideos.forEach(videoItem => {
                    // SDK接口：创建成员视频UI组件
                    let videoUI = CRVideo_CreatVideoObj({
                        poster: './image/pc/meeting_be_closed.jpg',
                        style: {
                            objectFit: 'cover' //object-fit属性
                        }
                    });
                    videoUI.videoElement = videoUI.handler();
                    videoUI.setVideo(member.userID, videoItem.videoID);
                    // videoUI.setVideo(member.userID, -1); //订阅默认摄像头
                    videoUI.setNickNameStyle(this.nameStyle);
                    videoUI.dblClickFullScreen(1); // 设置双击全屏
                    let videoIDStr = '';
                    if (openedIDs.length > 1) {
                        videoIDStr = `-${videoItem.videoID}`;
                    }
                    if (member.userID == MeetingDemo.Login.userID) {
                        videoUI.setNickNameContent(`我${videoIDStr}`);
                    } else {
                        videoUI.setNickNameContent(`${member.nickname}${videoIDStr}`);
                    }
                    videoUIs.push(videoUI);
                    videoItem.videoUI = videoUI;

                    const userIcon = MeetingDemo.MemberMgr.createUserIcon(member.userID, videoItem.videoID);
                    videoUI.videoElement.appendChild(userIcon); // 添加控制图标
                });

                return videoUIs;
            }
            // 成员视频状态变化
            memberVideoStatusChange(UID, oldStatus, newStatus) {
                let nickname = '';
                // 更新状态
                MeetingDemo.MemberMgr.members.forEach(memberItem => {
                    if (memberItem.userID == UID) {
                        memberItem.videoStatus = newStatus;
                        nickname = UID == MeetingDemo.Login.userID ? '我' : memberItem.nickname;
                    }
                });
                // 更新状态图标
                MeetingDemo.MemberMgr.refreshUserIconStatus(UID);
                // 弹框通知
                if (newStatus == 3 && oldStatus == 2) { //打开
                    !!nickname && MeetingDemo.tipLayer(`${nickname} 的摄像头已打开`, 1500);
                } else if (oldStatus == 3 && newStatus == 2) {
                    !!nickname && MeetingDemo.tipLayer(`${nickname} 的摄像头已关闭`, 1500);
                }
                if (MeetingDemo.Record.isMyRecord) MeetingDemo.Record.updateRecordContents();
            }
            // 点击工具栏本地摄像头开关按钮
            onClickCamOnOffBtn(dom) {
                if ($('#openCamBtn').hasClass('disabled')) {
                    MeetingDemo.tipLayer('操作太频繁啦');
                    return;
                };
                $('#openCamBtn').addClass('disabled');
                setTimeout(() => {
                    $('#openCamBtn').removeClass('disabled');
                }, 1000);
                const $deviceCam = $('[data-userid=' + MeetingDemo.Login.userID + '] .deviceCam');
                const vStatus = CRVideo_GetVideoStatus(MeetingDemo.Login.userID); // SDK接口：获取成员的视频状态
                console.log("(demo) Get video status result: " + vStatus);
                if (vStatus == 1 || vStatus == "VNULL") {
                    MeetingDemo.tipLayer("没有可打开的视频设备")
                } else if (vStatus == 2 || vStatus == 0 || vStatus == "VUNKNOWN" || vStatus == "VCLOSE") {
                    CRVideo_OpenVideo(MeetingDemo.Login.userID); // SDK接口：打开成员摄像头
                } else if (vStatus == 3 || vStatus == "VOPEN") {
                    CRVideo_CloseVideo(MeetingDemo.Login.userID); // SDK接口：关闭成员摄像头
                    $('#openCamBtn').addClass('cam_open').removeClass('cam_close');
                    $deviceCam.removeClass('deviceCamActive');
                }
            }
            // 点击成员状态图标里的摄像头图标
            onClickUserIconCam(dom) {
                const $this = $(dom);
                const UID = $this.parent().data('userid');
                if ($this.hasClass('deviceCamActive')) { //当前打开状态
                    CRVideo_CloseVideo(UID); // SDK接口：关闭摄像头
                    if (UID == MeetingDemo.Login.userID) {
                        // 关闭时直接设置图标，开启时等通知来了再设置图标
                        $this.removeClass('deviceCamActive');
                        $('#openCamBtn').addClass('cam_open').removeClass('cam_close');
                    }
                } else { //当前关闭状态
                    CRVideo_OpenVideo(UID); // SDK接口：打开摄像头
                }
            }
            // 点击成员状态图标里的拍照按钮
            onClickUserIconSave(dom) {
                const $this = $(dom);
                const UID = $this.parent().data('userid');
                const videoID = $this.parent().data('videoid');
                let videoUI = null;

                MeetingDemo.MemberMgr.members.forEach(member => {
                    if (member.userID == UID) {
                        member.allVideos.forEach(videoItem => {
                            if (videoItem.videoID == videoID) {
                                videoUI = videoItem.videoUI;
                            }
                        })
                    }
                })

                // 直接保存照片
                const Date = MeetingDemo.getDate();
                const picName = `${UID}-${videoID}-${Date.year}-${Date.month}-${Date.day}-${Date.hour}-${Date.minute}-${Date.second}.png`;
                videoUI.savePic(picName);

                // 展示照片
                const base64Src = videoUI.savePicToBase64('png');
                const img = document.createElement("img"); // 图片显示容器
                img.src = base64Src;
                // img.width = videoUI.handler().children[1].clientWidth;
                img.height = videoUI.handler().children[1].clientHeight;
                img.style.position = 'absolute';
                img.style.left = '50%';
                img.style.top = '50%';
                img.style.transform = 'translate(-50%,-50%)';
                img.style.boxShadow = '0 0 8px 1px rgb(0,0,0,.4)';
                img.onclick = function (e) {
                    e.stopPropagation(); //阻止事件冒泡
                }
                const div = document.createElement('div'); // 遮罩
                div.style.position = 'fixed';
                div.style.width = '100%';
                div.style.height = '100%';
                div.style.backgroundColor = 'rgb(0,0,0,.4)';
                div.style.top = '0';
                div.style.left = '0';
                div.style.zIndex = '999';
                div.onclick = function () {
                    div.remove(document.body);
                }
                div.appendChild(img);
                document.body.appendChild(div);
            }
            // 切换分辨率或帧率
            setVideoConfig(obj) {
                CRVideo_SetVideoCfg(obj); // SDK接口：设置视频参数
            }
            // 设置默认摄像头（切换摄像头）
            setDefaultVideo(camID) {
                CRVideo_SetDefaultVideo(MeetingDemo.Login.userID, camID); // SDK接口：设置默认摄像头
            }
            // 设置是否启用多摄像头
            setEnableMutiVideo() {
                if (!!$('#mutiCam').prop('checked')) {
                    CRVideo_SetEnableMutiVideo(MeetingDemo.Login.userID, true); // SDK接口：设置是否启用多摄像头
                } else {
                    CRVideo_SetEnableMutiVideo(MeetingDemo.Login.userID, false);
                }
            }
        }
        MeetingDemo.Video = new _videoM();

        // 音频管理模块
        class _audioM {
            constructor() {
                this.initData();
                this.registerCallback();
            }
            initData() {
                // this.curMic = null; // 当前正在使用的麦克风ID
                // this.curSpeaker = null; // 当前正在使用的扬声器ID
            }
            registerCallback() {
                // SDK接口：通知 成员麦克风状态改变
                CRVideo_AudioStatusChanged.callback = (UID, oldStatus, newStatus) => {
                    this.memberAudioStatusChanged(UID, oldStatus, newStatus);
                }
                // SDK接口：通知 成员语音能量变化
                CRVideo_MicEnergyUpdate.callback = (userID, oldLevel, newLevel) => {
                    this.memberMicEnergyUpdate(userID, oldLevel, newLevel);
                }
            }
            // 更新音频设备列表并展示
            updateAudioDevices() {
                const audioCfg = CRVideo_GetAudioCfg(); // SDK接口：获取当前音频设备参数
                const micArr = CRVideo_GetAudioMicNames(); // SDK接口：获取所有麦克风信息
                const spkerArr = CRVideo_GetAudioSpkNames(); // SDK接口：获取所有扬声器信息

                // 麦克风
                let micArrOptionsStr = "";
                for (let i = 0; i < micArr.length; i++) {
                    if (audioCfg.micID == micArr[i].micID) {
                        micArrOptionsStr += `<option value="${micArr[i].micID}" selected="true">${micArr[i].micName}</option>`;
                    } else {
                        micArrOptionsStr += `<option value="${micArr[i].micID}">${micArr[i].micName}</option>`;
                    }
                }
                $("#select_mic").html(micArrOptionsStr);
                // this.curMic = $("#select_mic").val();

                // 扬声器
                let spkerArrOptionsStr = "";
                for (let i = 0; i < spkerArr.length; i++) {
                    if (audioCfg.speakerID == spkerArr[i].speakerID) {
                        spkerArrOptionsStr += `<option value="${spkerArr[i].speakerID}" selected=true>${spkerArr[i].speakerName}</option>`;
                    } else {
                        spkerArrOptionsStr += `<option value="${spkerArr[i].speakerID}">${spkerArr[i].speakerName}</option>`;
                    }
                }
                $("#select_speaker").html(spkerArrOptionsStr);
                // this.curSpeaker = $("#select_speaker").val();
            }
            // 打开用户的麦克风
            openMic(UID) {
                CRVideo_OpenMic(UID); // SDK接口：打开某个用户的麦克风
            }
            // 成员音频状态变化
            memberAudioStatusChanged(UID, oldStatus, newStatus) {
                let nickname = '';
                // 更新状态
                MeetingDemo.MemberMgr.members.forEach(memberItem => {
                    if (memberItem.userID == UID) {
                        memberItem.audioStatus = newStatus;
                        nickname = UID == MeetingDemo.Login.userID ? '我' : memberItem.nickname;
                    }
                });
                // 更新图标
                MeetingDemo.MemberMgr.refreshUserIconStatus(UID);
                // 弹框通知
                if (newStatus == 3 && oldStatus == 2) {
                    !!nickname && MeetingDemo.tipLayer(`${nickname} 的麦克风已打开`, 1500);
                } else if (newStatus == 2 && oldStatus == 3) {
                    !!nickname && MeetingDemo.tipLayer(`${nickname} 的麦克风已关闭`, 1500);
                }
            }
            // 成员语音能量变化
            memberMicEnergyUpdate(UID, oldLevel, newLevel) {
                let $deviceMic = $('[data-userid=' + UID + '] .deviceMic');
                if (newLevel == 0) {
                    $deviceMic.removeClass('deviceMicEnergy');
                } else {
                    $deviceMic.addClass('deviceMicEnergy');
                }
            }
            // 点击工具栏本地麦克风开关按钮
            onClickMicOnOffBtn(dom) {
                const $deviceMic = $('[data-userid=' + MeetingDemo.Login.userID + '] .deviceMic');
                const aStatus = CRVideo_GetAudioStatus(MeetingDemo.Login.userID); // SDK接口：获取成员的麦克风状态
                if (aStatus == 1) { // 1: ANULL  没有麦克风
                    return MeetingDemo.tipLayer("没有可打开的音频设备");
                } else if (aStatus == 2 || aStatus == 0) { // 0: AUNKNOWN 2:ACLOSE  麦克风状态为未知或关闭
                    CRVideo_OpenMic(MeetingDemo.Login.userID); // SDK接口：打开麦克风
                } else if (aStatus == 3) { // 3：AOPEN  麦克风状态为开启
                    CRVideo_CloseMic(MeetingDemo.Login.userID); // SDK接口：关闭麦克风
                    $('#openMicBtn').addClass('mic_close').removeClass('mic_open');
                    $deviceMic.removeClass('deviceMicActive');
                }
            }
            // 点击工具栏扬声器播放/暂停按钮
            onClickSpeakerOnOffBtn(dom) {
                if ($('#openSpeakerBtn').hasClass('speaker_open')) {
                    CRVideo_PauseSpeaker();
                    $('#openSpeakerBtn').addClass('speaker_close').removeClass('speaker_open');
                } else {
                    CRVideo_PlaySpeaker();;
                    $('#openSpeakerBtn').addClass('speaker_open').removeClass('speaker_close');
                }
            }
            // 点击成员状态图标里面的麦克风按钮
            onClickUserIconMic(dom) {
                const $this = $(dom);
                const UID = $this.parent().data('userid');
                if ($this.hasClass('deviceMicActive')) { //当前为打开状态
                    CRVideo_CloseMic(UID); // SDK接口：关闭麦克风
                    if (UID == MeetingDemo.Login.userID) {
                        // 关闭时直接设置图标，开启时等通知来了再设置图标
                        $this.removeClass('deviceMicActive');
                        $('#openMicBtn').addClass('mic_close').removeClass('mic_open');
                    }
                } else { //当前为关闭状态
                    CRVideo_OpenMic(UID); // SDK接口：打开麦克风
                }
            }
            // 设置音频参数（切换扬声器或麦克风）
            setAudioCongfig(cfg) {
                CRVideo_SetAudioCfg(cfg); // SDK接口：设置音频参数
            }
        }
        MeetingDemo.Audio = new _audioM();

        // 屏幕共享模块
        class _screenM {
            constructor() {
                this.initData();
                this.registerCallback();
            }
            initData() {
                this.isStarting = false; // 是否正在启动中
                this.isSharing = false; // 是否正在共享中
                this.isMySharing = false; // 当前是否是我在共享
                this.screenShareUI = null;
            }
            registerCallback() {
                // SDK接口：通知 房间中开启了屏幕共享
                CRVideo_NotifyScreenShareStarted.callback = (sharer) => {
                    if (MeetingDemo.Media.isPlaying) MeetingDemo.Media.onClickStopMediaBtn(); // 如果当前在播放影音，则停止播放

                    this.isSharing = true;
                    this.isStarting = false;
                    if (sharer == MeetingDemo.Login.userID) {
                        this.isMySharing = true;
                        $('#openScreenBtn').addClass('sharing');
                    }
                    this.screenShareUI = CRVideo_CreatScreenShareObj({
                        poster: './image/pc/meeting_get_screen.jpg'
                    });
                    $(".page_share_box").html(this.screenShareUI.handler());
                    this.screenShareUI.setVideo(sharer);
                    this.screenShareUI.setNickNameStyle(MeetingDemo.Video.nameStyle);  // 设置昵称样式
                    this.screenShareUI.setNickNameContent(CRVideo_GetMemberNickName(sharer) + "的屏幕");
                    this.screenShareUI.dblClickFullScreen(1); // 设置双击全屏

                    $(".page_meet_video").hide();
                    $(".page_share_box").show();
                    if (MeetingDemo.Record.isMyRecord) MeetingDemo.Record.updateRecordContents(); // 更新云端录制内容

                    MeetingDemo.exitFullscreen();
                }
                // SDK接口：通知 房间内屏幕共享结束
                CRVideo_NotifyScreenShareStopped.callback = (sharer) => {
                    this.isSharing = false;
                    $('#openScreenBtn').removeClass('sharing');
                    if (sharer == MeetingDemo.Login.userID) {
                        this.isMySharing = false;
                    }
                    $(".page_meet_video").show();
                    $(".page_share_box").hide().html('');
                    if (MeetingDemo.Record.isMyRecord) MeetingDemo.Record.updateRecordContents(); // 更新云端录制内容
                    MeetingDemo.exitFullscreen();
                }
                // SDK接口：回调 开启屏幕共享结果
                CRVideo_StartScreenShareRslt.callback = (sdkErr) => {
                    if (sdkErr == 0) {
                        // ...
                    } else {
                        this.isStarting = false;
                        this.isSharing = false;
                        this.isMySharing = false;
                        $(".page_meet_video").show();
                        $(".page_share_box").hide().html('');
                        if (sdkErr == 8002) {
                            MeetingDemo.tipLayer(`开启屏幕共享失败，用户拒绝授予屏幕权限！`)
                        } else if (sdkErr == 8004) {
                            MeetingDemo.tipLayer(`开启屏幕共享失败，浏览器不支持获取屏幕！`)
                        } else {
                            MeetingDemo.tipLayer(`开启屏幕共享失败! 错误码：${sdkErr}`)
                        }
                    }
                }
            }
            // 点击屏幕共享按钮
            onClickScreenShareBtn(dom) {
                if (MeetingDemo.Media.isStarting || MeetingDemo.Media.isPlaying) { // 当前没有在播放影音
                    MeetingDemo.tipLayer(`当前房间正在播放影音，不可以共享屏幕！`)
                    return;
                } else if (this.isSharing) { // 当前在屏幕共享中
                    if ($(".page_share_box").css("display") == "none") {
                        $(".page_share_box").show();
                        $(".page_meet_video").hide();
                    } else {
                        $(".page_share_box").hide();
                        $(".page_meet_video").show()
                    }
                } else { // 当前没有在屏幕共享或播放影音
                    CRVideo_StartScreenShare(); // SDK接口：开始屏幕共享
                    this.isStarting = true;
                }
            }
            // 点击开启标注按钮
            onClickStartMarkBtn(e) {
                console.log('开启标注');
                e.stopPropagation();
                return false;
            }
            // 点击结束共享按钮
            onClickStopScreenShareBtn(e) {
                console.log('结束共享');
                e && e.stopPropagation();
                if (this.isSharing && this.isMySharing) {
                    CRVideo_StopScreenShare(); // SDK接口：结束屏幕共享
                }
                return false;
            }

        }
        MeetingDemo.Screen = new _screenM();

        // 影音共享模块
        class _mediaM {
            constructor() {
                this.initData();
                this.registerCallback();
            }
            initData() {
                this.mediaShareUIObj = null;
                this.isStarting = false;
                this.isPlaying = false;
                this.isMyPlaying = false;
            }
            registerCallback() {
                // SDK接口：通知 房间内开始播放影音
                CRVideo_NotifyMediaStart.callback = (UID) => {
                    this.isPlaying = true;
                    this.isStarting = false;
                    if (UID == MeetingDemo.Login.userID) {
                        $('#openMediaBtn').addClass('playing');
                        this.isMyPlaying = true;
                    } else {
                        this.mediaShareUIObj = CRVideo_CreatMediaObj({
                            poster: './image/pc/meeting_playing_mp3.jpg',
                        });
                        $(".page_share_box").html(this.mediaShareUIObj.handler());
                        this.mediaShareUIObj.setVideo(UID);
                        this.mediaShareUIObj.setNickNameStyle(MeetingDemo.Video.nameStyle);  // 设置昵称样式
                        this.mediaShareUIObj.setNickNameContent(CRVideo_GetMemberNickName(UID) + "播放的影音");
                        this.mediaShareUIObj.dblClickFullScreen(1); // 设置双击全屏
                    }

                    $(".page_meet_video").hide();
                    $(".page_share_box").show();
                    if (MeetingDemo.Record.isMyRecord) MeetingDemo.Record.updateRecordContents(); // 更新云端录制内容

                    MeetingDemo.exitFullscreen();
                }
                // SDK接口：通知 房间内影音播放停止
                CRVideo_NotifyMediaStop.callback = (UID) => {
                    this.stopPlayingHandler();
                    if (MeetingDemo.Record.isMyRecord) MeetingDemo.Record.updateRecordContents(); // 更新云端录制内容
                }
            }
            // 点击影音共享按钮
            onClickMediaShareBtn() {
                console.log(`影音共享`);
                if (MeetingDemo.Screen.isSharing) {
                    MeetingDemo.tipLayer(`当前房间正在屏幕共享中，不可同时共享影音！`)
                    return;
                } else if (this.isPlaying) {
                    if ($(".page_share_box").css("display") == "none") {
                        $(".page_share_box").show();
                        $(".page_meet_video").hide();
                    } else {
                        $(".page_share_box").hide();
                        $(".page_meet_video").show()
                    }
                } else {
                    this.mediaShareUIObj = window.CRVideo_CreatMediaObj({ // SDK接口：创建影音共享UI组件
                        poster: './image/pc/meeting_playing_mp3.jpg',
                        controls: true, //视频控件是否需要浏览器自带的控制条,
                        // loop: true, //循环播放 
                    });
                    this.mediaShareUIObj.dblClickFullScreen(1); // 设置双击全屏

                    $('.page_share_box').html(this.mediaShareUIObj.handler());

                    var evt = new MouseEvent("click", {
                        bubbles: false,
                        cancelable: true,
                        view: window
                    });
                    $('#mediaFileInput')[0].dispatchEvent(evt);
                }
            }
            // 载入影音文件
            onLoadMediaFiles(dom) {
                if (MeetingDemo.Screen.isSharing) {
                    MeetingDemo.tipLayer(`当前房间正在屏幕共享中，不可同时共享影音！`);
                    $('#openMediaBtn').removeClass('playing');
                    $('#mediaFileInput')[0].value = null;
                    this.mediaShareUIObj = null;
                    this.mediaShareUIObj = null;
                    this.isStarting = false;
                    this.isPlaying = false;
                    this.isMyPlaying = false;
                    return;
                }
                $(".page_share_box").show();
                $(".page_meet_video").hide();
                this.isStarting = true;

                const file = dom.files[0];
                const isPlaying = window.CRVideo_StartPlayMedia(this.mediaShareUIObj, file, 0, 1); // SDK接口：开始播放影音
                if (!isPlaying) {
                    MeetingDemo.tipLayer('不支持该影音格式！')
                    this.stopPlayingHandler();
                }
            }
            // 点击停止播放按钮
            onClickStopMediaBtn(e) {
                console.log('结束共享');
                e && e.stopPropagation();
                if (this.isPlaying && this.isMyPlaying) {
                    // $('#mediaFileInput')[0].value = null;
                    // $('.page_share_box').html('');
                    // this.mediaShareUIObj = null;
                    this.stopPlayingHandler();
                    CRVideo_StopPlayMedia(); // SDK接口：结束屏幕共享
                }
                return false;
            }
            // 停止播放的处理
            stopPlayingHandler() {
                MeetingDemo.exitFullscreen();
                $('#openMediaBtn').removeClass('playing');
                $(".page_share_box").hide();
                $(".page_meet_video").show();
                $('.page_share_box').html('');
                $('#mediaFileInput')[0].value = null;
                this.mediaShareUIObj = null;
                this.isStarting = false;
                this.isPlaying = false;
                this.isMyPlaying = false;
            }
        }
        MeetingDemo.Media = new _mediaM();

        // 录制管理模块
        class _recordM {
            constructor() {
                this.initData();
                this.registerCallback();
            }
            initData() {
                this.isSvrRecording = false; // 是否正在录制中
                this.isMyRecord = false; // 是否是我开启的录制
                this.videoMixerID = 1000; // 视频混图器起始ID
                this.MixerCfgObj = { // 音视频混图器规格配置对象，详见文档
                    height: 720,
                    width: 1280,
                    frameRate: 15,
                    bitRate: 2000 * 1000,
                    defaultQP: 18,
                }
                this.userRatio = 16 / 9; // 成员视频的默认比例
                this.isLiveStream = false; // 是否开启了直播推流
                this.recordTimer = -1; // 录制计时器
            }
            registerCallback() {
                // SDK接口：通知 云端录制状态变化
                CRVideo_SvrRecordStateChanged.callback = (state) => {
                    switch (state) {
                        case 0: // 未开始（已结束）
                            $('#page_meet_record').addClass('icon').removeClass('icon-recording').find('#recordBtn span').text('录制');
                            $('#liveStreamBtn').attr('disabled', false);
                            $('#liveStreamVal').attr('disabled', false);
                            clearInterval(this.recordTimer);
                            this.recordTimer = -1;
                            this.isSvrRecording = false;
                            this.isMyRecord = false;
                            break;
                        case 1: // 启动中
                            $('#liveStreamBtn').attr('disabled', true);
                            $('#liveStreamVal').attr('disabled', true);
                            $("#recordBtn span").text("启动中");
                            break;
                        case 2: // 录制中
                            $('#liveStreamBtn').attr('disabled', true);
                            $('#liveStreamVal').attr('disabled', true);
                            clearInterval(this.recordTimer);
                            let keepTime = 0;
                            this.recordTimer = setInterval(() => {
                                keepTime++;
                                const second = keepTime % 60;
                                const secondStr = second >= 10 ? second.toString() : `0${second}`;
                                const minute = parseInt(keepTime / 60);
                                const minuteStr = minute >= 10 ? minute.toString() : `0${minute}`;
                                $('#page_meet_record').addClass('icon icon-recording').find('#recordBtn span').text(`${minuteStr}:${secondStr}`);
                            }, 1000);
                            this.isSvrRecording = true;
                            break;
                        default:
                            break;
                    }
                }
            }
            // 更新云端录制状态
            updateRecordStatue() {
                //     0, 没有创建
                //     1, 正在开启
                //     2, 正在运行
                //     4, 正在结束
                const status = CRVideo_GetSvrMixerState();  // SDK接口：获取当前云端录制状态
                switch (status) {
                    case 0:
                        this.isSvrRecording = false;
                        $('#recordBtn span').text('录制');
                        break;
                    case 2:
                        $('#liveStreamBtn').attr('disabled', true);
                        $('#liveStreamVal').attr('disabled', true);
                        this.isSvrRecording = true;
                        $('#page_meet_record').addClass('icon-recording')
                        $('#recordBtn span').text('录制中');
                        break;
                    default:
                        this.isSvrRecording = false;
                        break;
                }
            }
            // 开关直播推流
            toggleLiveStream(dom) {
                if ($(dom).prop('checked')) {
                    this.isLiveStream = true;
                    $('#liveStreamVal').show(300);
                } else {
                    this.isLiveStream = false;
                    $('#liveStreamVal').hide(300);
                }
            }
            // 录像文件配置
            getMutiMixerCfgs() {
                const MutiMixerCfgs = [],
                    videoMixerCfg = {};

                // 视频文件录制参数
                videoMixerCfg.id = this.videoMixerID; // 视频混图器ID
                videoMixerCfg.streamTypes = 3; // 1 单音频 2 单视频  3 音视频
                videoMixerCfg.cfg = this.MixerCfgObj; // 混图器配置
                MutiMixerCfgs.push(videoMixerCfg);
                return MutiMixerCfgs;
            }
            // 录像内容
            getMutiMixerContents() {

                const MutiMixerContents = [],
                    videoContents = [], // 视频文件录制内容
                    videoItems = [], // 每个视频，有人可能有多个视频
                    AllMembers = MeetingDemo.MemberMgr.members;

                AllMembers.forEach(member => {
                    member.allVideos.forEach(item => {
                        videoItems.push({
                            userID: item.userID,
                            nickname: member.nickname,
                            videoID: item.videoID
                        });
                    })
                });

                let W = this.MixerCfgObj.width, // 视频文件总宽度
                    H = this.MixerCfgObj.height, // 视频文件总高度
                    x = 1, // 每行图像数
                    y = 1, // 每列图像数
                    w = 1280, // 每个图像的宽度
                    h = 720,  // 每个图形的高度
                    left = 0, // 画面离视频文件最左侧的距离
                    top = 0; // 画面离视频文件最顶部的距离

                // 计算画面数量和大小
                if (videoItems.length === 1) {
                    x = y = 1;
                    w = W;
                    h = H;
                } else if (videoItems.length === 2) {
                    x = 2;
                    y = 1;
                    w = parseInt(W / 2);
                    h = parseInt(w / this.userRatio);
                } else if (videoItems.length <= 4) {
                    x = 2;
                    y = 2;
                    w = parseInt(W / x);
                    h = parseInt(w / this.userRatio);
                } else {
                    x = 3;
                    y = 3;
                    w = parseInt(W / x);
                    h = parseInt(H / y);
                }

                videoItems.forEach((item, i) => {

                    // 计算画面位置
                    const startTop = (H - h * y) / 2;
                    const tmpTop = y == 1 ? 0 : parseInt(i / x);
                    top = startTop + tmpTop * h;
                    left = (i % x) * w;

                    // 添加画面内容 - 摄像头画面
                    videoContents.push({
                        type: 0,
                        left: left,
                        top: top,
                        width: w,
                        height: h,
                        param: {
                            "camid": `${item.userID}.${item.videoID}`
                        },
                        keepAspectRatio: 1
                    });

                    // 添加画面内容 - 文字（昵称）
                    videoContents.push({
                        type: 7,
                        left: left + 40,
                        top: top + 30,
                        param: {
                            text: `<span style="font-size:14px;color:#e21918">${item.nickname}-${item.videoID}号摄像头</span>`
                        }
                    });

                    // 如果没开摄像头，添加文字（摄像头已关闭）
                    if (CRVideo_GetVideoStatus(item.userID) == 2) {
                        videoContents.push({
                            type: 7,
                            left: left + w / 2 - 100,
                            top: top + h / 2 - 15,
                            param: {
                                text: `<span style="font-size:30px;color:#e21918">摄像头已关闭</span>`
                            }
                        })
                    }

                });

                // 添加画面内容 - 时间戳
                videoContents.push({
                    type: 4,
                    left: 35,
                    top: 3,
                    width: 175,
                    height: 32,
                    keepAspectRatio: 1,
                });

                // 如果有屏幕共享，暂时只录屏幕共享（也可以屏幕共享和视频都录，就看怎么布局了，这里为了简单就只录屏幕共享了）
                if (MeetingDemo.Screen.isSharing) {
                    while (videoContents.length > 0) {
                        videoContents.pop();
                    }
                    videoContents.push({
                        type: 5,
                        left: 0,
                        top: 0,
                        width: W,
                        height: H,
                    })
                }
                // 影音共享同理
                if (MeetingDemo.Media.isPlaying) {
                    while (videoContents.length > 0) {
                        videoContents.pop();
                    }
                    videoContents.push({
                        type: 3,
                        left: 0,
                        top: 0,
                        width: W,
                        height: H,
                    })
                }

                MutiMixerContents.push({
                    id: this.videoMixerID,
                    content: videoContents
                })

                return MutiMixerContents;
            }
            // 录像输出
            getMutiMixerOutputs() {
                const MutiMixerOutputs = [], videoOutputs = [],
                    Date = MeetingDemo.getDate(),
                    dirName = `/${Date.year}-${Date.month}-${Date.day}/`,
                    timeStr = `${Date.year}-${Date.month}-${Date.day}_${Date.hour}-${Date.minute}-${Date.second}`,
                    meetID = MeetingDemo.RoomMgr.meetID,
                    rtmpUrl = $('#liveStreamVal').val().trim();

                // /2020-11-03/2020-11-03_16-40-03_H5_48888999.mp4
                const fileName = `${dirName}${timeStr}_H5_${meetID}.mp4`;
                // 添加输出 - 视频文件
                videoOutputs.push({
                    type: 0,
                    filename: fileName
                });
                // 添加输出 - 直播推流
                if (this.isLiveStream && !!rtmpUrl) {
                    videoOutputs.push({
                        type: 1,
                        liveUrl: rtmpUrl
                    });
                }
                MutiMixerOutputs.push({
                    id: this.videoMixerID,
                    output: videoOutputs
                })
                return MutiMixerOutputs;
            }
            // 点击录制按钮
            onClickRecordBtn() {
                if (!this.isSvrRecording) {
                    this.startRecord();
                } else {
                    this.stopRecord();
                }
            }
            // 开始录制
            startRecord() {
                const MutiMixerCfgs = this.getMutiMixerCfgs(),
                    MutiMixerContents = this.getMutiMixerContents(),
                    MutiMixerOutputs = this.getMutiMixerOutputs();
                CRVideo_StartSvrMixer(MutiMixerCfgs, MutiMixerContents, MutiMixerOutputs); // SDK接口：开始录制

                $('#liveStreamBtn').attr('disabled', true);
                $('#liveStreamVal').attr('disabled', true);
                $("#recordBtn span").text("启动中");
                $('#page_meet_record').removeClass('icon icon-recording');
                this.recordTimer != -1 && clearInterval(this.recordTimer);
                this.recordTimer = -1;
                this.isMyRecord = true;
            }
            // 结束录制
            stopRecord() {
                CRVideo_StopSvrMixer(); // SDK接口：停止录制
                this.isSvrRecording = false;
                this.isMyRecord = false;
                this.videoMixerID++;
                $('#liveStreamBtn').attr('disabled', false);
                $('#liveStreamVal').attr('disabled', false);
                $("#recordBtn span").text("录制");
                $('#page_meet_record').addClass('icon').removeClass('icon-recording');
                this.recordTimer != -1 && clearInterval(this.recordTimer);
                this.recordTimer = -1;
            }
            // 更新录制内容
            updateRecordContents() {
                CRVideo_UpdateSvrMixerContent(this.getMutiMixerContents());
            }
        }
        MeetingDemo.Record = new _recordM();

        // 邀请模块
        class _inviteM {
            constructor() {
                this.initData();
                this.registerCallback();
            }
            initData() {
                this.inviteID = null;
                this.curLayerIndex = null;
            }
            // 注册SDK回调
            registerCallback() {
                // SDK接口：回调 发送邀请成功
                CRVideo_InviteSuccess.callback = (inviteID, cookie) => {
                    console.log(`发送邀请成功：${inviteID}`);
                    this.inviteID = inviteID;
                    this.curLayerIndex = layer.open({
                        closeBtn: 0,
                        title: [`邀请`, 'font-size:0.16rem'],
                        content: `邀请发送成功，等待对方应答...`,
                        btn: ['取消邀请'],
                        btn1: function (index) {
                            CRVideo_CancelInvite(inviteID, '', ''); // SDK接口：取消邀请
                            layer.close(index);
                        },
                    })
                }
                // SDK接口：回调 发送邀请失败
                CRVideo_InviteFail.callback = (inviteID, sdkErr, cookie) => {
                    console.log(`发送邀请失败：${sdkErr}，${inviteID}`);
                    this.inviteID = inviteID;
                    MeetingDemo.alertLayer(`发送邀请失败：${sdkErr}`);
                }
                // SDK接口：回调 取消邀请成功
                CRVideo_CancelInviteSuccess.callback = (inviteID, cookie) => {
                    MeetingDemo.tipLayer(`邀请已取消`);
                }
                // SDK接口：回调 取消邀请失败
                CRVideo_CancelInviteFail.callback = (inviteID, sdkErr, cookie) => {
                    MeetingDemo.tipLayer(`取消邀请失败:${sdkErr}`);
                }
                // SDK接口：通知 我发送的邀请被对方接受
                CRVideo_NotifyInviteAccepted.callback = (inviteID, userExtDat) => {
                    console.log(`对方接受邀请：${inviteID}，${userExtDat}`);
                    layer.close(this.curLayerIndex);
                    MeetingDemo.tipLayer(`对方已接受邀请，正在进入房间...`);
                }
                // SDK接口：通知 我发送的邀请被对方拒绝
                CRVideo_NotifyInviteRejected.callback = (inviteID, reason, userExtDat) => {
                    console.log(`对方拒绝邀请：${inviteID}，${reason}，${userExtDat}`);
                    layer.close(this.curLayerIndex);
                    if (reason == '604') {
                        MeetingDemo.alertLayer(`邀请失败，对方无应答！`);
                    } else {
                        MeetingDemo.alertLayer(`对方拒绝了本次邀请！`);
                    }
                }
                // SDK接口：通知 收到别人的邀请
                CRVideo_NotifyInviteIn.callback = (inviteID, inviterUserID, userExtDat) => {
                    console.log(`收到来自 ${inviterUserID} 的邀请：${userExtDat}，${inviteID}`);
                    if (MeetingDemo.RoomMgr.isMeeting) {
                        CRVideo_RejectInvite(inviteID, userExtDat, `用户 ${inviterUserID} 邀请你加入房间，当前正在房间中，已自动拒绝该邀请。`);
                        return;
                    };
                    this.curLayerIndex = layer.open({
                        closeBtn: 0,
                        title: [`邀请`, 'font-size:0.16rem'],
                        content: `用户 ${inviterUserID} 邀请你加入房间 ${userExtDat}，</br>是否接受邀请？`,
                        btn: ['接受', '拒绝'],
                        btn1: function (index) {
                            CRVideo_AcceptInvite(inviteID, userExtDat, userExtDat);
                            layer.close(index);
                        },
                        btn2: function (index) {
                            CRVideo_RejectInvite(inviteID, userExtDat, userExtDat);
                        }
                    })
                }
                // SDK接口：通知 对方取消了邀请
                CRVideo_NotifyInviteCanceled.callback = (inviteID, reason, userExtDat) => {
                    console.log(`对方取消邀请：${inviteID}，${reason}，${userExtDat}`);
                    layer.close(this.curLayerIndex);
                    MeetingDemo.tipLayer('对方取消了邀请！')
                }
                // SDK接口：回调 接受对方的邀请成功
                CRVideo_AcceptInviteSuccess.callback = (inviteID, cookie) => {
                    console.log(`接受邀请成功：${inviteID}`);
                    MeetingDemo.tipLayer(`接受邀请成功，进入房间：${cookie}`);
                    MeetingDemo.RoomMgr.meetID = cookie;
                    MeetingDemo.RoomMgr.enterMeetingFun();
                }
                // SDK接口：回调 接受对方的邀请失败
                CRVideo_AcceptInviteFail.callback = (inviteID, sdkErr, cookie) => {
                    console.log(`接受邀请失败：${sdkErr}，${inviteID}`);
                    MeetingDemo.tipLayer(`接受邀请失败：${sdkErr}，${inviteID}`);
                }
                // SDK接口：回调 拒绝对方的邀请成功
                CRVideo_RejectInviteSuccess.callback = (inviteID, cookie) => {
                    console.log(`拒绝邀请成功：${inviteID}`);
                    MeetingDemo.tipLayer(`已拒绝对方的邀请`);
                }
                // SDK接口：回调 拒绝对方的邀请失败
                CRVideo_RejectInviteFail.callback = (inviteID, sdkErr, cookie) => {
                    console.log(`拒绝邀请失败：${sdkErr}，${inviteID}`);
                    MeetingDemo.tipLayer(`拒绝邀请失败：${sdkErr}，${inviteID}`)
                }

            }
            // 点击邀请按钮
            openInviteBtn() {
                layer.prompt({
                    formType: 2,
                    value: '',
                    title: '请输入被邀请者的用户ID',
                    area: ['200px', '20px'] //自定义文本域宽高
                }, function (value, index, elem) {
                    CRVideo_Invite(value, MeetingDemo.RoomMgr.meetID);
                    layer.close(index);
                });
            }
            // 发送邀请
            sendInvite(UID) {
                CRVideo_Invite(UID, MeetingDemo.RoomMgr.meetID);
            }
        }
        MeetingDemo.Invite = new _inviteM();

        // 聊天模块
        class _chatM {
            constructor() {
                this.initData();
                this.registerCallback();
            }
            initData() { }
            registerCallback() {
                // SDK接口：回调 发送消息的结果
                CRVideo_SendMeetingCustomMsgRslt.callback = (errCode, cookie) => {
                    if (errCode == 0) { //发送成功
                        $("input[name=chat_msg]").val("");
                    } else {
                        MeetingDemo.tipLayer(`消息发送失败！${errCode}`);
                    }
                }
                // SDK接口：通知 收到广播消息
                CRVideo_NotifyMeetingCustomMsg.callback = (fromUserID, stringMsg) => {
                    let jsonMsg = {};
                    try {
                        jsonMsg = JSON.parse(stringMsg);
                    } catch (e) {
                        throw e;
                    }
                    if (jsonMsg.CmdType && jsonMsg.CmdType == 'IM') this.recivedChatMsg(fromUserID, jsonMsg.IMMsg);
                }
                // SDK接口：通知 收到透明通道消息
                CRVideo_NotifyCmdData.callback = (userID, data) => {
                    MeetingDemo.tipLayer(`收到 ${userID} 发送的透明通道消息：${JSON.stringify(data)}`, 2000);
                }
                // SDK接口：通知  收到IM消息（已废弃，兼容旧版）
                CRVideo_NotifyIMMsg.callback = (userID, text, sendTime) => {
                    this.recivedChatMsg(userID, text);
                }
            }
            // 点击工具栏聊天按钮
            onClickChatBtn() {
                if ($(".page_meet_chat").css("display") == "none") {
                    $(".page_meet_chat").show();
                    $(".page_chat_box").show();
                } else {
                    $(".page_meet_chat").hide();
                    $(".page_chat_box").hide();
                }
            }
            // 点击聊天发送按钮
            onClickSendBtn() {
                const msgText = $("input[name=chat_msg]").val();
                const stringMsg = JSON.stringify({
                    "CmdType": "IM",
                    "IMMsg": msgText
                })
                CRVideo_SendMeetingCustomMsg(stringMsg); // SDK接口：发送会议内广播消息
                // CRVideo_SendIMMsg(msgText); // SDK接口：发送IM消息（已废弃，兼容旧版）
                $("input[name=chat_msg]").val("");
            }
            // 收到聊天消息
            recivedChatMsg(userID, textmsg) {
                const msg = textmsg;
                const nickname = CRVideo_GetMemberNickName(userID); // SDK接口：获取用户的昵称
                const htmlEle =
                    `<li>
                            <span class="chat_content">
                                <span class="chat_name">${nickname}</span>：${msg}
                            </span>
                        </li>
                        </br>`
                $(".page_chat_box > ul").append(htmlEle);
                $(".page_chat_box > ul")[0].scrollTop = $(".page_chat_box > ul")[0].scrollHeight; // 让滚动条自动滚动到底部

                if ($(".page_chat_box").css("display") == "none") {
                    MeetingDemo.tipLayer("收到了新的聊天消息...", 1000);
                }
            }
        }
        MeetingDemo.Chat = new _chatM();
    });




</script>

</html>